package org.yinxianren.java.test.thread;

import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 一个锁可能伴随着多个条件。这些条件声明在Condition接口中。
 * 这些条件的目的是允许线程拥有锁的控制并且检查条件是否为true，如果是false，那么线程将被阻塞，直到其他线程唤醒它们。
 * Condition接口提供一种机制，阻塞一个线程和唤醒一个被阻塞的线程。
 *
 * 在并发编程中，生产者与消费者是经典的问题。我们有一个数据缓冲区，一个或多个数据生产者往缓冲区存储数据，
 * 一个或多个数据消费者从缓冲区中取出数据，正如在这一章中前面所解释的一样。
 */
public class DemoCondition {
    public static void main(String[] args) {
        FileMock mock=new FileMock(100, 10);
        Buffer buffer=new Buffer(20);

        Producer2 producer=new Producer2(mock, buffer);
        Thread threadProducer=new Thread(producer,"Producer");

        Consumer2[] consumers = new Consumer2[3];
        Thread[] threadConsumers = new Thread[3];
        for (int i=0; i<3; i++){
            consumers[i]=new Consumer2(buffer);
            threadConsumers[i]=new Thread(consumers[i],"Consumer "+i);
        }

        threadProducer.start();

        for (int i=0; i<3; i++){
            threadConsumers[i].start();
        }


    }
}
class FileMock {
    private String[] content;
    private int index;

    public FileMock(int size, int length){
        content=new String[size];
        for (int i=0; i<size; i++){
            StringBuilder buffer=new StringBuilder(length);
            for (int j=0; j<length; j++){
                int indice=(int)Math.random()*255;
                buffer.append((char)indice);
            }
            content[i]=buffer.toString();
        }
        index=0;
    }


    public boolean hasMoreLines(){
        return index<content.length;
    }


    public String getLine(){
        if (this.hasMoreLines()) {
            System.out.println("Mock: "+(content.length-index));
            return content[index++];
        }
        return null;
    }

}


class Buffer {
    /*
    一个类型为LinkedList<String>，名为buffer的属性，用来存储共享数据
    一个类型为int，名为maxSize的属性，用来存储缓冲区的长度
    一个名为lock的ReentrantLock对象，用来控制修改缓冲区代码块的访问
    两个名分别为lines和space，类型为Condition的属性
    一个Boolean类型，名为pendingLines的属性，表明如果缓冲区中有行
     */
    private LinkedList<String> buffer;
    private int maxSize;
    private ReentrantLock lock;
    private Condition lines;
    private Condition space;
    private boolean pendingLines;


    public Buffer(int maxSize) {
        this.maxSize=maxSize;
        buffer=new LinkedList<>();
        lock=new ReentrantLock();
        lines=lock.newCondition();
        space=lock.newCondition();
        pendingLines=true;
    }

    public void insert(String line) {
        lock.lock();
        try {
            while (buffer.size() == maxSize) {
                space.await();
            }
            buffer.offer(line);
            System.out.printf("%s: Inserted Line: %d\n", Thread.currentThread().getName(),buffer.size());
            lines.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


    public String get() {
        String line=null;
        lock.lock();
        try {
            while ((buffer.size() == 0) &&(hasPendingLines())) {
                lines.await();
            }
            if (hasPendingLines()) {
                line = buffer.poll();
                System.out.printf("%s: Line Readed: %d\n",Thread.currentThread().getName(),buffer.size());
                space.signalAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return line;
    }

    public void setPendingLines(boolean pendingLines) {
        this.pendingLines=pendingLines;
    }

    public boolean hasPendingLines() {
        return pendingLines || buffer.size()>0;
    }
}

class Producer2 implements Runnable {

    private FileMock mock;
    private Buffer buffer;

    public Producer2 (FileMock mock, Buffer buffer){
        this.mock=mock;
        this.buffer=buffer;
    }

    @Override
    public void run() {
        buffer.setPendingLines(true);
        while (mock.hasMoreLines()){
            String line=mock.getLine();
            buffer.insert(line);
        }
        buffer.setPendingLines(false);
    }
}

class Consumer2 implements Runnable {

    private Buffer buffer;
    public Consumer2 (Buffer buffer) {
        this.buffer=buffer;
    }

    @Override
    public void run() {
        while (buffer.hasPendingLines()) {
            String line=buffer.get();
            processLine(line);
        }
    }

    private void processLine(String line) {
        try {
            Random random=new Random();
            Thread.sleep(random.nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}